#ifndef EPIDEMICCA_H
#define EPIDEMICCA_H

#include <memory>
#include <random>
#include <ctime>
#include <QDebug>
#include "grid.h"
#include "ca.h"
#include "neighbours.h"

using namespace std;


class EpidemicCA: public CA
{


public:


    float proportion;//[0,1]
    int a;//[0,200]
    int b;//[0,200]

    float p_infection;
    float p_death;


    EpidemicCA(shared_ptr<Grid> g,float p_infection,float p_death):CA()
    {
        step=0;
        current_states=g;
        new_states=g->copy();
        this->p_infection=(p_infection>1.0f)?1.0f:p_infection;
        this->p_death=(p_death>1.0f)?1.0f:p_death;
    }

    void setParam(float proportion,int a, int b)
    {
        for(int i=0;i<current_states->height();++i)
            for(int j=0;j<current_states->width();++j)
                current_states->tiles[i][j]->value=new_states->tiles[i][j]->value=SUSCEPTIBLE;

        step=0;
        this->a=(a>200)?200:a;
        this->b=(b>200)?200:b;
        this->proportion=(proportion>1.0f)?1.0f:proportion;
        int N=round(new_states->width()*new_states->height()*proportion);
        srand(time(0));
        int x,y;
        while(N>0)
        {

            x=rand()%new_states->width();
            y=rand()%new_states->height();
            if(current_states->tiles[y][x]->value!=0)++N;
            current_states->tiles[y][x]->value=rand()%(a+b)+1;
            --N;
        }
    }


    ~EpidemicCA()
    {
    }


    bool next()
    {

        bool changed=false;
        random_device random;
        mt19937 generate_rand(random());

        auto grid=dynamic_pointer_cast<Grid>(current_states);
        for(int i=0;i<current_states->height();++i)
            for(int j=0;j<current_states->width();++j)
            {
                new_states->tiles[i][j]->value=current_states->tiles[i][j]->value;
                auto nbs=neighbours::vn(current_states,i,j);
                if(get(i,j)==DEAD)
                {
                    nbs.erase( std::remove_if( nbs.begin(), nbs.end(),
                                  [&](shared_ptr<Tile>& t)->bool{
                                    return EpidemicCA::get(t->y/Tile::TILE_SIZE,t->x/Tile::TILE_SIZE)!=SUSCEPTIBLE;
                    }), nbs.end() );
                    if(nbs.size()>2)
                    {
                        new_states->tiles[i][j]->value=0;//SUSCEPTIBLE
                        changed=true;                    }

                }
                else if(get(i,j)==SUSCEPTIBLE)
                {

                    for(auto x:nbs)
                    {

                        if(get(x->y/Tile::TILE_SIZE,x->x/Tile::TILE_SIZE)==INFECTED)
                        {

                            if(generate_canonical<float,2>(generate_rand)>p_infection)break;
                            new_states->tiles[i][j]->value=1;//infected
                            changed=true;
                            break;
                        }
                    }

                }
                else if(get(i,j)==INFECTED)
                {

                    if(generate_canonical<float,2>(generate_rand)<p_death)
                        new_states->tiles[i][j]->value=-1;
                    else new_states->tiles[i][j]->value+=1;
                    changed=true;
                }
                else if(get(i,j)==IMMUNE)
                {
                    if(new_states->tiles[i][j]->value<a+b)new_states->tiles[i][j]->value+=1;
                    else new_states->tiles[i][j]->value=0;
                    changed=true;
                }
            }

        current_states.swap(new_states);
        ++step;

        return changed;

    }

    void start(){
        while(next());
    }

    void start(int steps)
    {
        for(int i=0;i<steps;++i)next();
    }

    int getStep()const
    {
        return step;
    }

    State get(int i,int j)const
    {
        if(current_states->tiles[i][j]->value==-1)return DEAD;
        else if(current_states->tiles[i][j]->value==0)return SUSCEPTIBLE;
        else if(current_states->tiles[i][j]->value<=a) return INFECTED;
        else return IMMUNE;
    }

    void printState(bool value=false)const
    {
        if(value)
        {
            for(int i=0;i<current_states->height();++i)
            {
                for(int j=0;j<current_states->width();++j)
                    printf("%d ",current_states->tiles[i][j]->value);
                printf("\n");
            }
        }
        else
        {
            for(int i=0;i<current_states->height();++i)
            {
                for(int j=0;j<current_states->width();++j)
                    printf("%d ",get(i,j));
                printf("\n");
            }
        }
        printf("\n\n");

    }

};


#endif // EPIDEMICCA_H
